iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0

前面幾天,我們看過了 Ktor 的啟動,路由,以及靜態內容和 HTML 畫面的生成。

今天我們來看看另一個後端框架非常基礎的功能:生成 json 格式的 API 內容。

首先,撰寫之前要先安裝 ContentNegotiation

gradle 要加上

    implementation("io.ktor:ktor-server-content-negotiation:$ktor_version")
    implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")

然後框架內加上

fun Application.module() {
    configureRouting()
    configureSerialization()
}

fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json()
    }
}

之前看路由時,我們已經看過 install 的實作,我們看看 ContentNegotiation

public val ContentNegotiation: RouteScopedPlugin<ContentNegotiationConfig> = createRouteScopedPlugin(
    "ContentNegotiation",
    ::ContentNegotiationConfig
) {
    convertRequestBody()
    convertResponseBody()
}

convertRequestBody 實作了轉換請求內容的邏輯,試著將請求的內容轉換成物件

internal fun PluginBuilder<ContentNegotiationConfig>.convertRequestBody() {
    onCallReceive { call ->
        val registrations = pluginConfig.registrations
        val requestedType = call.receiveType

        if (requestedType.type in pluginConfig.ignoredTypes) {
            LOGGER.trace(
                "Skipping for request type ${requestedType.type} because the type is ignored."
            )
            return@onCallReceive
        }

        transformBody { body: ByteReadChannel ->
            val requestContentType = try {
                call.request.contentType().withoutParameters()
            } catch (parseFailure: BadContentTypeFormatException) {
                throw BadRequestException(
                    "Illegal Content-Type header format: ${call.request.headers[HttpHeaders.ContentType]}",
                    parseFailure
                )
            }

            val charset = call.request.contentCharset() ?: Charsets.UTF_8
            for (registration in registrations) {
                return@transformBody convertBody(body, charset, registration, requestedType, requestContentType)
                    ?: continue
            }

            LOGGER.trace("No suitable content converter found for request type ${requestedType.type}")
            return@transformBody body
        }
    }
}

如果不是 pluginConfig.ignoredTypes 的話,則會執行 transformBody

    public suspend fun transformBody(transform: suspend TransformBodyContext.(body: ByteReadChannel) -> Any) {
        val receiveBody = context.subject as? ByteReadChannel ?: return
        val typeInfo = context.call.receiveType
        if (typeInfo == typeInfo<ByteReadChannel>()) return

        val transformContext = TransformBodyContext(typeInfo)
        context.subject = transformContext.transform(receiveBody)
    }

以這邊的 transform 邏輯來說,會試著用 Charsets.UTF_8 執行 convertBody

private suspend fun convertBody(
    body: ByteReadChannel,
    charsets: Charset,
    registration: ConverterRegistration,
    receiveType: TypeInfo,
    requestContentType: ContentType
): Any? {
    if (!requestContentType.match(registration.contentType)) {
        LOGGER.trace(
            "Skipping content converter for request type ${receiveType.type} because " +
                "content type $requestContentType does not match ${registration.contentType}"
        )
        return null
    }

    val converter = registration.converter
    val convertedBody = try {
        converter.deserialize(charsets, receiveType, body)
    } catch (cause: Throwable) {
        throw BadRequestException("Failed to convert request body to ${receiveType.type}", cause)
    }

    return when {
        convertedBody != null -> convertedBody
        !body.isClosedForRead -> body
        receiveType.kotlinType?.isMarkedNullable == true -> NullBody
        else -> null
    }
}

到這邊,我們可以發現這裡會試著用 Kotlin 的 deserialize ,來將收到的內容轉換成物件。

今天針對 ContentNegotiation 的部分,我們先看到這邊。明天我們來看看 json 的部分是怎麼實作的。


上一篇
Day 11:生成 HTML Body 和 H1 標籤的 body 與 h1
下一篇
Day 13:json() 如何註冊一個 Json 格式的處理器
系列文
深入解析 Kotlin 專案 Ktor 的程式碼,探索 Ktor 的強大功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言